# -*- coding: utf-8 -*-
'''
@author: olivia.dou
Created on: 1/11/2021 18:07
desc:
'''
import logging,time
import re
import traceback
from datetime import datetime
import pandas as pd
import requests
from jsonpath import jsonpath
from base import globalvars as glo
from base.getdata import get_header
from proj_spec.DAS.std_def import get_std_fin_item_info
from proj_spec.DAS.biz import get_biz_std_table_name
from proj_spec.DAS.template import get_std_template_code, get_code_of_biz_in_template, \
    get_fin_item_info, get_template_biz_id, get_template_fin_item_info
from proj_spec.DAS.misc import notify
from util.db_util import execute_query
from util.jsonmatch import my_list_cmp
from util.number_util import get_number_from_string, approx_Equal
from util.string_util import convert_to_float_from_str
#from proj_spec.DAS.std import get_fin_item_const_value


def compute_value(nodes,use_adjust=False):
    """获取nodes对应目标节点的CoA

    :param nodes: 模板映射关系中的relation_node节点的列表

    :return: CoA值
    """

    results = []
    try:
        for node in nodes:

            if use_adjust and node['adjustValue'] is not None:
                value = get_number_from_string(node['adjustValue'])
            else:
                value = get_number_from_string(node['value'])

            if value is None:
                continue
            if node['func'] is not None:
                value = eval(node['func'] + "(value)")
            if node['factor']==-1:
                value = -value
            if node['div']!=0:
                results.append(value*node['mul']/node['div'])
            else:
                results.append(0)

        if results==[]:
            return None
        else:
            return sum(results)
    except Exception as e:
        logging.error("compute_value: %s"%e)
        traceback.print_exc()
        return None


def build_result_node(computed_value):
    """构造结果节点，for usage of subMin/subMinNegative：最后模板计算用的是+/-组内结果的绝对值，因此若在计算组内函数结果时返回所有获胜节点，后续逻辑复杂
    改为在计算组内函数结果时仅返回一个节点，值为传入的最终计算值，分子分母均设为1，操作符+，不使用绝对值

    :param computed_value: 传入最终计算值

    :return: 构造的节点
    """
    result_node = {}
    result_node['value'] = computed_value
    result_node['mul'] = 1
    result_node['div'] = 1
    result_node['func'] = None
    result_node['abs'] = 0
    result_node['factor'] = 1
    result_node['groupFunction'] = None

    return result_node


def compare_by_group_function(file_id, mapping_id, nodes, function_name):
    """进行组内比较
    由于subMin的最终计算值逻辑，函数修正为以分组函数比较组内值，另需计算分组值的函数
    subMin/subMinNegative:绝对值、操作符、分子分母参与组内运算，用加和的绝对值比较(最终计算值为获胜组绝对值/绝对值相反数)
    sumPositive/sumNegative:操作符、分子分母参与组内运算，用加和实际值比较
    其他组内函数：分子分母参与组内运算，用加和的绝对值比较

    :param file_id: 文件id
    :param mapping_id: 映射id
    :param nodes: 节点的列表
    :param function_name: 函数名

    :return: 以分组函数比较，在组内胜出的节点
    """
    try:
        #result = 0
        results = []
        #value_index = -1# 胜出的值所在索引
        std_values = [get_std_value(file_id, mapping_id,node['srcTemplateId'], node['sourceTemplateFinItemId'],node['itemDetail']) for  node in nodes]

        #values = [abs(std_values[i])*nodes[i]['mulFactor']/nodes[i]['divFactor'] if std_values[i] is not None else 0 for i in range(len(std_values))]
        # values = [abs(std_values[i] * nodes[i]['mulFactor'] / nodes[i]['divFactor']) if std_values[i] is not None else 0
        #          for i in range(len(std_values))]
        if function_name in ['subMin','subMinNegative']: #组内不同子分组结果（绝对值）比较，取小的那个子分组
            values = [std_values[i] * nodes[i]['mulFactor'] / nodes[i]['divFactor'] if std_values[i] is not None else 0
                for i in range(len(std_values))]  # 乘以系数
            values = [abs(values[i]) if nodes[i]['formular']==1 else values[i] for i in range(len(std_values))] #加上绝对值函数
            values = [-values[i] if nodes[i]['operator']=='-' else values[i] for i in range(len(std_values))] #加上操作符
            # 找出sub_group_0的元素在nodes中的索引位置
            df_nodes = pd.DataFrame(nodes)
            #df_sub_group0 = df_nodes[df_nodes['includeMin']==0].dropna(how='any')
            df_sub_group0 = df_nodes[df_nodes['minIncluded'] == 0]
            df_sub_group1 = df_nodes[df_nodes['minIncluded'] == 1]

            index_0 = df_sub_group0.index
            value_sub_group0 = pd.DataFrame(values).iloc[index_0][0].to_list()
            index_1 = df_sub_group1.index
            value_sub_group1 = pd.DataFrame(values).iloc[index_1][0].to_list()

            # if function_name=='subMin':
            #     result_factor = 1
            # else:
            #     result_factor = -1
            if abs(sum(value_sub_group0)) <= abs(sum(value_sub_group1)):
                return df_sub_group0.to_dict(orient='records')
                # # 构造组内计算值结果节点
                # results.append(build_result_node(abs(sum(value_sub_group0)) * result_factor))
            else:
                return df_sub_group1.to_dict(orient='records')
                # results.append(build_result_node(abs(sum(value_sub_group1)) * result_factor))
        elif function_name in ['sumPositive','sumNegative']:
            #Todo: 整个subMin/subMinNegative的计算部分
            values = [std_values[i] * nodes[i]['mulFactor'] / nodes[i]['divFactor'] if std_values[i] is not None else 0
                for i in range(len(std_values))]  # 乘以系数
            #values = [abs(values[i]) if nodes[i]['formular']==1 else values[i] for i in range(len(std_values))] #加上绝对值函数
            values = [-values[i] if nodes[i]['operator']=='-' else values[i] for i in range(len(std_values))] #加上操作符

            if function_name=='sumPositive' and sum(values)>=0 or (function_name=='sumNegative' and sum(values)<0):
                return nodes
            else:
                return []
        else:
            values = [
                abs(std_values[i] * nodes[i]['mulFactor'] / nodes[i]['divFactor']) if std_values[i] is not None else 0
                for i in range(len(std_values))]
            for i,node in enumerate(nodes):
                #factor = factors[node['operator']]
               #value = get_std_value(file_id,mapping_id,node['sourceTemplateFinItemId'])
                std_value = std_values[i]
                value=values[i]
                #value = std_value*node['mulFactor']/node['divFactor']

                # includeMin: 条件if (min(a,b,c,d) ) = a then +a + b + c + d
                # else b + c + d
                # 同一个组内的用组内函数进行比较求最小值（比较的时候用绝对值)，如果一个字段被选中为include字段，当它是最小的时候，参与映射计算，否则不参与；
                # 不是include字段的其他字段则参与映射计算
                if function_name == 'includeMin' and node['minIncluded'] == 1 and abs(value) == min(values) \
                        or function_name == 'includeMin' and node['minIncluded'] == 0 \
                        or function_name=='MAX' and abs(value) == max(values) \
                        or function_name == 'MIN' and abs(value) == min(values):
                        # result = result+value
                        # value_index=i
                    if std_value is not None: #空值所在节点不返回
                        node['value']=std_value
                        results.append(node)
        return results
    except Exception as e:
        logging.error("compute_group_function: %s,file_id %s,mapping_id %s,function_name %s"%(e,file_id,mapping_id,function_name))


def copy_node_keys(source_node):
    """将源节点的字段做字段名转换后,返回新节点

    :param source_node: 源节点

    :return: 将源节点的字段做字段名转换后的新节点
    """
    target_node = {}
    if 'value' not in source_node.keys() or source_node['value'] is None:
        target_node['value']=None
    else:
        target_node['value']=float(source_node['value'])

    target_node['mul']=source_node['mulFactor']
    target_node['div'] = source_node['divFactor']

    if source_node['formular']==1:
        target_node['func']='abs'
        target_node['abs']=1
    else:
        target_node['func']=None
        target_node['abs']=0
    if source_node['operator']=='-':
        target_node['factor']=-1
    else:
        target_node['factor']=1

    target_node['bizName']=source_node['sourceBizName']
    target_node['itemName']=source_node['sourceItemName']
    target_node['itemDetail'] = source_node['itemDetail']
    target_node['srcTemplateItemId'] = source_node['sourceTemplateFinItemId']
    target_node['adjustValue'] = source_node['adjustValue']
    # target_node['groupName'] = source_node['groupName']
    # target_node['groupFunction'] = source_node['groupFunction']
    return target_node


def _get_source_nodes(file_id, mapping_id, input_relation_nodes):
    """获取输入关系节点中的源节点

    :param file_id: 文件id
    :param mapping_id: 映射id
    :param input_relation_nodes: 输入关系节点

    :return: 节点的列表
    """

    # from proj_spec.DAS.Standardized import get_std_fin_item_info
    from proj_spec.DAS.std import get_fin_item_const_value

    groups = list(set([node['groupName'] for node in input_relation_nodes if node['groupName'] is not None and node['groupName']!=""]))
    intermedium_relation_nodes = [node for node in input_relation_nodes if node['groupName'] is None or node['groupName']==""]
    # 中间节点集合，相当于文件模板计算该映射关系页面该目标科目追踪时列出的结果节点列表

    if len(groups)>0: #有分组

        for group_name in groups:
            #group_result={}
            group_function = jsonpath(input_relation_nodes, "$[?(@.groupName=='%s')].groupFunction" % group_name)
            if group_function is not False:
                group_function=group_function[0]#同一组的组内函数应一致
            group_nodes = jsonpath(input_relation_nodes, "$[?(@.groupName=='%s')]" % group_name)
            if (group_function!='' and group_function is not None) and group_name is not None:#有组内函数
                #计算组内函数
                #index,value=compute_group_function(file_id,mapping_id,group_nodes,group_function)
                win_nodes = compare_by_group_function(file_id, mapping_id, group_nodes, group_function)

                if win_nodes is not None:
                    intermedium_relation_nodes.extend(win_nodes)
            else: #组内优先级

                nodes = jsonpath(input_relation_nodes, "$[?(@.groupName=='%s')]" % group_name)

                groups_dict = {}
                for i in sorted(list(set(jsonpath(nodes,"$[*].groupPriority")))):# set无序，需排序
                    group_value = []
                    groups_dict[i]=jsonpath(nodes,"$[?(@.groupPriority==%s)]"%i)
                    for node in groups_dict[i]:
                        value = get_std_value(file_id, mapping_id, node['srcTemplateId'],
                                              node['sourceTemplateFinItemId'], node['itemDetail'])
                        if value is not None and value != '':
                            node['value'] = value
                            intermedium_relation_nodes.append(node)
                            group_value.append(value)

                    if group_value!=[]: # 该组至少有一个科目有值,则忽略优先级更低的组
                        break


    results = []
    for node in intermedium_relation_nodes:

        if node['validCondition']!=0: # 有条件

            if node['refTemplateFinItemId'] is None:
                #默认参考源科目值
                ref_fin_item_id = node['sourceTemplateFinItemId']
            else:
                ref_fin_item_id = node['refTemplateFinItemId']
            # 查找参考科目值
            ref_value = get_std_value(file_id,mapping_id,node['srcTemplateId'],ref_fin_item_id,node['itemDetail'])
            #if ref_value is None  or node['validCondition']==1 and ref_value<0 or node['validCondition']==2 and ref_value>0:#不符合条件
            if ref_value is None or node['validCondition'] == 1 and ref_value < 0 or node['validCondition'] == 2 and ref_value > 0:  # 不符合条件
                #node['value']=0
                # # Todo: 此处与业务逻辑无关，仅为匹配系统开发代码实现逻辑：如参考科目为本科目不满足条件，则结果中不列出该关系节点，如参考科目为其他科目不满足条件,且非禁用，则结果中个列出改关系节点
                # fin_item_id = get_fin_item_info(ref_fin_item_id)['id']
                # if node['refTemplateFinItemId'] is not None and node['refTemplateFinItemId']!=node['sourceTemplateFinItemId'] and get_std_fin_item_info("status",id=fin_item_id)==0:
                #     node['value']=None
                #     results.append(copy_node_keys(node))
                # else:
                #     continue
                continue
            else:
                node['value'] = get_std_value(file_id, mapping_id,node['srcTemplateId'], node['sourceTemplateFinItemId'], node['itemDetail'])
                node['adjustValue'] = get_coa_adjust_value(file_id,mapping_id,node['sourceTemplateFinItemId'])
                results.append(copy_node_keys(node))
        elif 'value' not in node.keys():
            # 常量值
            if node['sourceCode']=='test_const_rate': #Todo: 实际常量值类型的科目可配置多个，名称/编码是自定义的  此处由于测试场景需求比较简单，暂指定科目名称‘固定比率’，科目code'test_const_code'
                node['value'] = get_fin_item_const_value('test_const_rate')
                node['adjustValue'] = get_coa_adjust_value(file_id, mapping_id, node['sourceTemplateFinItemId'])
            else:
                node['value']=get_std_value(file_id,mapping_id,node['srcTemplateId'],node['sourceTemplateFinItemId'],node['itemDetail'])
                node['adjustValue'] = get_coa_adjust_value(file_id, mapping_id, node['sourceTemplateFinItemId'])
            results.append(copy_node_keys(node))
        else: #分组时已获取值
            node['adjustValue'] = get_coa_adjust_value(file_id, mapping_id, node['sourceTemplateFinItemId'])
            results.append(copy_node_keys(node))

    return results


def get_std_value(file_id, mapping_id, src_template_id, src_template_fin_item_id, item_detail=None):
    """模板数据追踪获取标准化值：根据fin_item_id查找源模板中科目的标准化值：从该文件标准化数据中查找该科目的值

    :param file_id: 文件id
    :param mapping_id: 映射id
    :param src_template_id: 源模板id: 源模板直接传参而不是通过映射关系查找：因自映射的源模板非映射关系源模板
    :param src_template_fin_item_id: 源模板科目id
    :param item_detail: 节点的itemDetail键值
    Todo:
    :use_adjust: True: 有调整值情况下使用调整值，默认为False: 忽略调整值

    :return: 标准化值
    """

    from proj_spec.DAS.File import get_vfile_id, get_file_restate_setting  #解决循环导入的问题
    from proj_spec.DAS.std import get_item_std_data,get_item_std_data_b,get_item_std_data_c,get_std_fin_item_info

    vfile_id = get_vfile_id(file_id)
    #mapping_info = get_mapping_info(id=mapping_id)
    #src_template_id = mapping_info['src_template_id']
    client_code,template_code = get_std_template_code(src_template_id)

    #Todo:biz_code和fin_item_code直接传参，减少查询耗时？
    src_biz_code = get_code_of_biz_in_template(client_code, template_code, src_template_fin_item_id)
    std_table_name = get_biz_std_table_name(client_code, src_biz_code)
    fin_item_code = get_fin_item_info(src_template_fin_item_id)['std_code']

    #如果科目已被禁用，则返回None
    item_status = get_std_fin_item_info("status",std_code=fin_item_code)
    if item_status==1:
        return None

    # #过滤没有标准化数据追踪的数据
    # if std_table_name !='' and std_table_name is not None and get_std_trace_data(file_id,fin_item_code,std_table_name)==[]:
    if std_table_name != '' and std_table_name is not None == []: # 本期无值下期文件有重述上期值的科目，重述值写入本期重述，该科目亦没有标准化追踪数据
        return None

    # 获取文件的使用重述值设置
    restate_usage = get_file_restate_setting(file_id)

    # 模板数据追踪获取标准化值不应通过业务确定标准表名，应在A,B,C表中同时查询，根据Upt最新来获取
    if client_code=='sys':
        value_a = get_item_std_data(vfile_id, fin_item_code,src_biz_code, restate_usage)
        value_b = get_item_std_data_b(vfile_id, fin_item_code,src_biz_code, item_detail, restate_usage)
        value_c = get_item_std_data_c(vfile_id, fin_item_code, item_detail,restate_usage)
        value_list = [value for value in [value_a,value_b,value_c] if value is not None]
        if value_list != []:
            datafrm = pd.DataFrame(value_list,columns=["value","upt"])
            datafrm.sort_values(by="upt", ascending=False,inplace=True)
            return datafrm.iloc[0]["value"]
        else:
            return None

    else: #自映射
        # if use_adjust is True:
        #     # 用作计算的输入值时，有调整值则用调整值
        #     adjust_value = get_coa_adjust_value(file_id,mapping_id,src_template_fin_item_id)
        # else:
        #     adjust_value = None
        #
        # if adjust_value is None:
        #     nodes = get_source_nodes(file_id, mapping_id, src_template_fin_item_id)
        #     value= compute_value(nodes)
        # else: # adjust_value not None:
        #     #if 1==0:
        #     value = adjust_value
        nodes = get_source_nodes(file_id, mapping_id, src_template_fin_item_id)
        value = compute_value(nodes)
    return value


def get_source_nodes(file_id, mapping_id, target_fin_item_id):
    """文件按照映射关系查找target_fin_item_id的对应source节点

    :param file_id: 文件id
    :param mapping_id: 映射id
    :param target_fin_item_id: 目标科目id

    :return: source节点
    """

    from base.get_config import get_header_file
    root_url3 = glo.get_value("url3")
    header1 = get_header(get_header_file(), "header1")

    mapping_dict = glo.get_value("mapping_dict")
    if mapping_dict is not None and mapping_id in mapping_dict.keys(): #已查询过的映射关系
        res = mapping_dict[mapping_id]
    else:
        mapping_url = root_url3 + "/api/v1/das/template/mapping/item?mappingId=%s" % mapping_id
        res = requests.get(mapping_url, headers=header1)
        if mapping_dict is not None:
            mapping_dict[mapping_id]=res
            glo.set_value("mapping_dict",mapping_dict)
        else:
            mapping_dict = {mapping_id:res}
            glo.set_value("mapping_dict", mapping_dict)
    assert res.status_code == 200, "获取映射关系失败： %s" % res.text

    relation_nodes =  jsonpath(res.json(), "$.relation[?(@.targetTemplateFinItemId==%s)]" % target_fin_item_id)
    #compute_value(file_id, mapping_id,relation_nodes)

    if relation_nodes is not False:
        expected_nodes = _get_source_nodes(file_id, mapping_id, relation_nodes)
        return expected_nodes
    else:
        return []


def validate_internal_template_interface(file_id, mapping_id, template_item_id, actual_json):
    """验证/api/v1/das/internal/template/result接口的正确性
    debug: 在template_data表中找到template data id, 再到template_data_detail表根据映射关系的目标节点的account_code找到template_item_id

    :param mapping_id: 映射关系id
    :param template_item_id: template_data_detail.template_item_id, =std_template_fin_item.id,模板业务与科目关联表id

    :return: 正确 True，错误 False
    """
    try:
        expected_nodes = get_source_nodes(file_id, mapping_id, template_item_id)
        expected_nodes_converted=[]
        for node in expected_nodes:
            if node['value'] is not None or node['adjustValue'] is not None: #调整值非空的情况也应该包含
                expected_nodes_converted.append(node)

        # 仅验证部分字段（如业务编码需要查询则忽略）
        if expected_nodes_converted !=[]: #预期不为空数组
            actual_nodes = (pd.DataFrame(actual_json['traceList'])[['abs','bizName','div','factor','func','itemDetail','itemName','mul','srcTemplateItemId','value','adjustValue']]).to_dict(orient='records')

            actual_nodes_converted = []
            for node in actual_nodes:
                if node['value'] is not None or node['adjustValue'] is not None:
                    node['value']=convert_to_float_from_str(node['value'])
                    node['adjustValue'] = convert_to_float_from_str(node['adjustValue'])
                    actual_nodes_converted.append(node)
            #assert my_list_cmp(expected_nodes_converted, actual_nodes_converted), "expected: %s, actual: %s" % (
            assert my_list_cmp(expected_nodes_converted, actual_nodes_converted,tolerance=0.0001), "expected: %s, actual: %s" % (
             expected_nodes_converted, actual_nodes_converted)
        else:
            assert actual_json['traceList']==[],"预期结果为空，实际不为空"
        return True
    except Exception as e:
        logging.error("validate_internal_template_interface: %s\nfile_id: %s,mapping_id:%s,template_item_id: %s" % (e, file_id, mapping_id, template_item_id))
        return False


def compute_values(nodes):
    """计算节点的汇总值，所有参数纳入计算
    首先判断节点的groupFunction是否为subMin/subMinNegative, 如果是，返回汇总值的绝对值/绝对值相反数，否则返回实际汇总值

    :param nodes: 纳入计算的所有节点

    :return: 计算结果
    """
    function_name = nodes[0]['groupFunction']  # 同一分组的组内函数应一致
    real_sum = compute_value(nodes)
    if function_name =="subMin":

        return abs(real_sum) if real_sum is not None else 0
    elif function_name == "subMinNegative":
        return -abs(real_sum) if real_sum is not None else 0
    else:
        return real_sum


def validate_template_data_track(company_code,fiscal_year,fiscal_period,mapping_id,file_id):
    """验证模板数据追踪正确性

    :param company_code: 公司代码, 如600690
    :param fiscal_year: 财年，如2018
    :param fiscal_period: 财季，如3,6,9,12
    :param mapping_id: 映射id
    :param file_id: 文件id

    :return: 正确 True，错误 False
    """
    from base.get_config import get_header_file

    logging.info("验证模板数据追踪：company_code %s,fiscal_year %s,fiscal_period %s,mapping_id %s,file_id %s"
                 %(company_code,fiscal_year,fiscal_period,mapping_id,file_id))

    logging.debug(datetime.now())
    root_url3 = glo.get_value("url3")
    header1 = get_header(get_header_file(), "header1")
    std_trace_url = root_url3 + "/api/v1/das/stdtrace?CompanyCode=%s&fy=%s&p=%s&showName=1&mappingId=%s&fileId=%s&_v=1575448376948" \
                    % (company_code, fiscal_year, fiscal_period, mapping_id, file_id)
    std_trace_res = requests.get(std_trace_url, headers=header1)

    if std_trace_res.status_code != 200:
        logging.error("模板数据追踪接口响应状态码不为200：%s" % std_trace_res.text)

    customerData = jsonpath(std_trace_res.json(), "$.customerData[*]")

    template_data_id = jsonpath(std_trace_res.json(), "$.templateDataId")
    if template_data_id is not False:
        template_data_id = template_data_id[0]
    else:
        logging.error("获取template data id失败")

    fail = False
    for data in customerData:#目标模板数据
        if 'code' not in data.keys() or data['code'] is None or 'value' not in data.keys() or data['value'] is None:
            # or data['value'] is None:
            # NA不可跳过！NA需验证是否真的为NA
            continue
        else:
            template_item_id = data['id'] #t_das_std_template_fin_item.id, t_das_template_data_detail.template_item_id

            customer_value = float(data['value'].replace(',', ''))
            # stdtr_item_url = root_url3 + "/api/v1/das/stdtrace/item/%s?fy=%s&p=%s&mappingId=%s&CompanyCode=%s&id=%s&fileId=%s&_v=1575368098937" \
            #                  % (template_item_id, fiscal_year, fiscal_period, mapping_id, company_code, template_item_id, file_id)


            tmpl_result_url = root_url3 + "/api/v1/das/internal/template/result/%s/trace/%s"%(template_data_id,template_item_id)

            time.sleep(0.3)
            tmpl_result_res = requests.get(tmpl_result_url, headers=header1)  #模板数据追踪中单一目标节点的追踪数据
            if tmpl_result_res.status_code != 200:
                logging.error("模板数据追踪项接口响应状态码不为200：%s" % tmpl_result_res.text)

            # 验证接口返回的正确性
            assert (validate_internal_template_interface(file_id,mapping_id,template_item_id,tmpl_result_res.json()))


            if (tmpl_result_res.json()==[]):
                continue #接口返回正确性已验证，如返回为空数组则跳过不需要计算结果
            #trace_list = jsonpath(tmpl_result_res.json(), "$.traceList[*]")

            # CoA常量科目traceList是空的
            compute_nodes = jsonpath(tmpl_result_res.json(), "$.traceList[*]")
            if compute_nodes is not False:
                submin_nodes = [node for node in compute_nodes if node['groupFunction'] in ['subMin','subMinNegative']
                                and node['groupName'] is not None and node['groupName']!='']
                groups = list(set([node['groupName'] for node in submin_nodes]))
                other_nodes =  [node for node in compute_nodes if node['groupFunction'] not in ['subMin','subMinNegative']
                                or node['groupName'] is None or node['groupName']==''] # ?

                trace_nodes = []

                for group_name in groups:
                    group_nodes = [node for node in submin_nodes if node['groupName']==group_name]
                    function_name = group_nodes[0]['groupFunction']  # 同一分组的组内函数应一致
                    group_value = compute_values(group_nodes)
                    # 构造节点
                    group_result = build_result_node(group_value)
                    trace_nodes.append(group_result)

                trace_nodes.extend(other_nodes)
                #trace_nodes = compute_nodes # 增加groupName字段前的workaround

                try:
                    # assert approx_Equal(sum([get_number_from_string(item) for item in trace_nodes]),
                    #                              customer_value)  # 近似阈值0.001
                    computed_value = compute_value(trace_nodes)
                    assert approx_Equal(computed_value,customer_value)  # 近似阈值0.001

                    # 验证text字段的数值正确（有调整值则用调整值）, 最终值使用调整的值（非调整值计算结果）
                    text_match = re.match("\d+-+([\(0-9,.\)]+) \(.+\)", data['text'])
                    if text_match is not None:
                        display_text = text_match.group(1)
                        display_value = convert_to_float_from_str(display_text)

                        # 若无调整值，显示值等于计算值
                        # 若有调整值，显示值等于调整值（若有调整设置，调整值来自调整设置，否则来自计算）
                        if data['isAdjusted'] ==1:

                            if data['isAdjustDefined']==0:  # 没有调整设置
                                adjust_value = compute_value(trace_nodes, use_adjust=True)
                                if adjust_value is not None:
                                    assert approx_Equal(adjust_value,display_value)
                                else:
                                    assert approx_Equal(computed_value,display_value)
                            else: # 有调整设置，adjustValue字段值由调整设置决定的验证包含在其他用例中，此处不做验证
                                adjust_value = get_coa_adjust_value(file_id, mapping_id, template_item_id)
                                assert approx_Equal(adjust_value,display_value)
                        else:
                            assert approx_Equal(computed_value,display_value)

                except Exception as e:
                    logging.error("模板数据追踪：company_code %s,fiscal_year %s,fiscal_period %s,mapping_id %s,file_id %s"
                                  %(company_code,fiscal_year,fiscal_period,mapping_id,file_id))
                    logging.error("模板计算结果不正确%s:%s,%s" % (e, jsonpath(trace_nodes,"$[*].value"), data['value']))
                    logging.debug(sum([get_number_from_string(item) for item in trace_nodes]))
                    logging.debug(customer_value)
                    fail = True

    return not fail


def get_template_data_id(file_id, mapping_group, mapping_name):
    """根据文件与映射关系名称获取模板数据id

    :param file_id: 文件id
    :param mapping_group: 映射关系分组
    :param mapping_name: 映射关系名称

    :return: 模板数据id
    """

    results = execute_query(
        "SELECT d.id FROM t_das_template_data d LEFT JOIN t_das_template_mapping m "
        "ON d.mapping_id=m.id WHERE  d.file_id='%s' AND m.mapping_group='%s' AND m.mapping_name='%s'" % (file_id, mapping_group, mapping_name))
    try:
        result = results[0][0]
        return result
    except Exception as e:
        logging.error("no result retrieved")
        return None


def _get_template_data_id(file_id,mapping_id):
    """根据文件id和mapping id获取模板数据id

    :param file_id:
    :param mapping_id:

    :return: 模板数据id
    """
    res = execute_query("""SELECT id FROM t_das_template_data WHERE file_id=%s AND mapping_id=%s"""%(file_id,mapping_id))
    try:
        result = res[0][0]
        return result
    except Exception as e:
        logging.error("_get_template_data_id fail: %s"%e)
        return None


def get_template_coa_adjust_def_info(file_id, mapping_group, mapping_name, client_code, template_code, biz_code,account_code, *args):
    """获取模板调整值定义表数据

    :param file_id: 文件id
    :param mapping_name: 模板关系分组
    :param mapping_name: 模板关系名称
    :param client_code: 客户编码
    :param template_code: 模板编码
    :param biz_code: 业务编码
    :param account_code: 调整的科目编码
    :param args: 传入需要获取字段名称

    :return: 模板调整值定义表中获取字段value的list
    """

    template_data_id = get_template_data_id(file_id, mapping_group, mapping_name)
    template_biz_id = get_template_biz_id(template_code, client_code, biz_code)
    try:
        if args[0] == "adjust_value":
            results = execute_query(
                "SELECT "
                "0+CAST(adjust_value AS CHAR)"  # 去除decimal中多余的0位，并将数据类型转换为字符型（6位小数位去除后变为1位小数位）
                "adjust_value FROM t_das_template_coa_adjust_def WHERE template_data_id='%s' AND template_biz_id='%s' AND account_code='%s' order by id" % (
                    template_data_id, template_biz_id, account_code))
            result = [str(results[i][0]) for i in range(len(results))]
            for i in range(len(result)):
                if result[i].count('.') > 0:
                    result[i] = result[i].rstrip('0').rstrip('.')
        else:
            results = execute_query(
            "SELECT * FROM t_das_template_coa_adjust_def WHERE template_data_id='%s' AND template_biz_id='%s' AND account_code='%s' order by id" % (
                template_data_id, template_biz_id, account_code), result_type='dict')
            result = [results[i][args[0]] for i in range(len(results))]
        return result
    except Exception as e:
        logging.error("get_template_coa_adjust_def_info:%s" % e)
        return None


def get_templatedata_adjust_info(file_id, mapping_group, mapping_name, client_code, template_code, biz_code, account_code, args):
    """获取模板科目调整值被计算后的调整数据

    :param file_id: 文件id
    :param mapping_name: 模板关系分组
    :param mapping_name: 模板关系名称
    :param client_code: 客户编码
    :param template_code: 模板编码
    :param biz_code: 业务编码
    :param account_code: 调整的科目编码
    :param args: 传入需要获取字段名称

    :return: 被计算的调整数据记录list
    """

    template_data_id = get_template_data_id(file_id, mapping_group, mapping_name)
    template_biz_id = get_template_biz_id(template_code, client_code, biz_code)

    return _get_templatedata_adjust_info(template_data_id, template_biz_id, account_code, args)


def _get_templatedata_adjust_info(template_data_id, template_biz_id, account_code,args):
    """获取模板科目调整值被计算后的调整数据

    :param template_data_id: 模板数据id
    :param template_biz_id: 模板业务id
    :param account_code:
    :param args: 传入需要获取字段名称

    :return: 被计算的调整数据记录list
    """

    try:
        if args == "adjust_value":
            results = execute_query(
                "SELECT "
                "0+CAST(adjust_value AS CHAR)"  # 去除decimal中多余的0位，并将数据类型转换为字符型（6位小数位去除后变为1位小数位）
                "adjust_value FROM t_das_template_coa_detail_adjust WHERE template_data_id='%s' AND template_biz_id='%s' AND account_code='%s' order by id" % (
                    template_data_id, template_biz_id, account_code))
            if len(results)>0:
                result = str(results[0][0])
                # if result.count('.') == 0:
                #     result = format(float(result), '.1f')
                if result.count('.') > 0:
                    result = result.rstrip('0').rstrip('.')  # 将含有小数点的整数去除小数点后的0，并去除小数点
            else:
                return None
        else:
            results = execute_query(
            "SELECT * FROM t_das_template_coa_detail_adjust WHERE template_data_id='%s' AND template_biz_id='%s' AND account_code='%s' order by id" % (
                template_data_id, template_biz_id, account_code), result_type='dict')
            #result = [results[i][args[0]] for i in range(len(results))]
            result = [results[i][args] for i in range(len(results))]
        return result
    except Exception as e:
        logging.error("_get_template_coa_adjust_info:%s" % e)
        return None


def get_coa_adjust_params(file_id,mapping_id,template_fin_item_id):
    """获取coa调整值接口需要的参数组合

    :param file_id: 文件id
    :param mapping_id: 映射id
    :param template_fin_item_id: template_fin_item_id

    :return: 对应的template_data_id,template_biz_id，account_code
    """

    template_data_id = _get_template_data_id(file_id, mapping_id)
    template_fin_item_info = get_template_fin_item_info(template_fin_item_id)
    if template_fin_item_info is not None:
        template_biz_id = template_fin_item_info['template_biz_id']
        fin_item_id = template_fin_item_info['fin_item_id']
        account_code = get_std_fin_item_info(field_names_str="std_code", id=fin_item_id)
        return template_data_id,template_biz_id,account_code
    else:
        return template_data_id,None,None


def get_coa_adjust_value(file_id,mapping_id,template_fin_item_id):
    """ 获取模板调整值

    :param file_id: 文件id
    :param mapping_id: 映射id
    :param template_fin_item_id: template_fin_item_id

    :return: 对应映射科目的调整值
    """
    template_data_id = _get_template_data_id(file_id, mapping_id)
    template_fin_item_info = get_template_fin_item_info(template_fin_item_id)
    if template_fin_item_info is not None:
        template_biz_id = template_fin_item_info['template_biz_id']
        fin_item_id = template_fin_item_info['fin_item_id']
        account_code = get_std_fin_item_info(field_names_str="std_code",id=fin_item_id)
        adjust_value = _get_templatedata_adjust_info(template_data_id, template_biz_id, account_code, "adjust_value")
        if adjust_value is not None:
            return float(adjust_value)
        else:
            return None
    else:
        return None


def get_templatedata_target_item_computedvalue(file_id, mapping_group, mapping_name, client_code, template_code, biz_code,account_code):
    """获取模板科目原始值

    :param file_id: 文件id
    :param mapping_name: 模板关系分组
    :param mapping_name: 模板关系名称
    :param client_code: 客户编码
    :param template_code: 模板编码
    :param biz_code: 业务编码
    :param account_code: 调整的科目编码

    :return: 模板科目原始值(保留2位小数)
    """

    template_data_id = get_template_data_id(file_id, mapping_group, mapping_name)
    template_biz_id = get_template_biz_id(template_code, client_code, biz_code)
    results = execute_query(
        "SELECT computed_value FROM `t_das_template_data_detail` WHERE `template_data_id` = '%s' AND `template_biz_id` = '%s' AND `account_code` = '%s' " % (
            template_data_id, template_biz_id, account_code))
    try:
        result = format(results[0][0], '0,.2f')  # 无占位输出，用逗号作为分隔符，保留2位小数
        return result
    except Exception as e:
        logging.error("no result retrieved")
        return None

def get_coa_review_status(report_year,report_period, mapping_id, company_code, file_id):
    """获取文件的coa审核状态

    :param report_year: 财年
    :param report_period: 财季
    :param mapping_id: 映射id
    :param company_code: 公司code
    :param file_id: 文件id

    :return: 审核状态（1/0）
    """
    from base.get_config import get_header_file
    root_url3 = glo.get_value("url3")
    header2 = get_header(get_header_file(), "header2")

    template_result_url = "/api/v1/das/internal/template/result/list"

    payload = {
         "mappingId": mapping_id,
         "mappingGroup": "",
         "entityCode": company_code,
         "orgId": "",
         "reportYear": report_year,
         "reportPeriod": report_period,
         "report": "",
         "status": ""
    }

    res = requests.post(root_url3 + template_result_url, headers=header2, json=payload)

    assert res.status_code==200,"获取模板审核列表失败：%s"%res.text

    file_review_status = jsonpath(res.json(),"$.list[?(@.fileId==%s)].status"%file_id)
    if file_review_status is not False:
        return  file_review_status[0]
    else:
        return None

def revert_coa_review(file_id, mapping_id):
    """撤回coa审核

    :param file_id: 文件id
    :param mapping_id: 映射id

    :return: None
    """
    from base.get_config import get_header_file
    root_url3 = glo.get_value("url3")
    header2 = get_header(get_header_file(), "header2")

    template_data_id = _get_template_data_id(file_id, mapping_id)
    revert_url = """/api/v1/das/internal/template/result/%s"""%template_data_id

    payload = {"action": "revert", "reason": "to validate template calculation"}

    res = requests.put(root_url3+revert_url,headers=header2,json=payload)
    assert res.status_code==200,"撤回coa审核失败：%s"%res.text

def approve_coa_review(file_id, mapping_id):
    """通过coa审核

    :param file_id: 文件id
    :param mapping_id: 映射id

    :return: None
    """
    from base.get_config import get_header_file
    root_url3 = glo.get_value("url3")
    header2 = get_header(get_header_file(), "header2")

    template_data_id = _get_template_data_id(file_id, mapping_id)
    revert_url = """/api/v1/das/internal/template/result/%s"""%template_data_id

    payload = {"action": "approve"}

    res = requests.put(root_url3+revert_url,headers=header2,json=payload)
    assert res.status_code==200,"coa审核失败：%s"%res.text

def adjust_calc(file_id, mapping_id):
    """触发指定模板数据的调整计算

    :param file_id: 文件id
    :param mapping_id: 映射id

    :return: None
    """
    from base.get_config import get_header_file
    root_url3 = glo.get_value("url3")
    header2 = get_header(get_header_file(), "header2")
    url = root_url3 + "/api/v1/das/internal/template/adjust/calc/start"
    template_data_id = _get_template_data_id(file_id, mapping_id)
    payload = {"templateDataId":template_data_id}

    res = requests.post(url, headers = header2, json = payload)
    assert res.status_code==200,"调整计算失败：%s"%res.text



def revoke_coa_approval(file_id):
    """撤销文件相关的所有已审核的coa，应只在前置步骤中有需求

    :param file_id: 文件id

    :return:
    """

    from base.get_config import get_header_file
    root_url = glo.get_value("url10")
    header2 = get_header(get_header_file(), "header2")

    payload = {"action":"revert","reason":"automation test"}

    # 查询文件相关的所有已审核coa，并予以撤销
    sql = "SELECT file_id, mapping_id FROM t_das_template_data WHERE file_id=%s and status=1"%file_id

    try:
        result = execute_query(sql)
        for file_id,mapping_id in result:
            data_id = _get_template_data_id(file_id, mapping_id)
            url = root_url + "/das/internal/template/result/%s"%data_id
            rsp = requests.put(url, headers = header2, json = payload)
            assert rsp.status_code==200,"撤销模板审核失败： file %s mapping %s，%s"%(file_id, mapping_id, rsp.text)
            notify("COA审核撤销：file %s mapping %s"%(file_id, mapping_id))
    except Exception as e:
        logging.error("revoke coa approval of file %s failed!"%file_id)
        raise







